package aupdg.logic.impl;

import aupdg.data.Log;
import aupdg.data.model.Edge;
import aupdg.data.model.Ldg;
import aupdg.data.model.Node;
import aupdg.data.model.Petrinet;
import aupdg.data.model.impl.AuControlDependenceEdge;
import aupdg.data.model.impl.AuDataDependenceEdge;
import aupdg.data.model.impl.AuEntryNode;
import aupdg.data.model.impl.AuIfNode;
import aupdg.data.model.impl.AuLdg;
import aupdg.data.model.impl.AuLdgData;
import aupdg.data.model.impl.AuPredicateNode;
import aupdg.data.model.impl.AuTransitionNode;
import aupdg.data.wrapper.ProMLog;
import aupdg.logic.LdgAlgorithm;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.processmining.framework.log.AuditTrailEntry;
import org.processmining.framework.log.AuditTrailEntryList;
import org.processmining.framework.log.LogReader;
import org.processmining.framework.log.ProcessInstance;
import org.processmining.framework.models.petrinet.PetriNet;
import org.processmining.framework.models.petrinet.Place;
import org.processmining.framework.models.petrinet.Transition;

/**
 * Implement the contract of LdgAlgorithm for generating an LDG.
 *
 * @author AA and CJ
 */
public class LdgAlgorithmForProM implements LdgAlgorithm {

    private Log myLog = null;
    //Entry node of the LDG
    private Node entryNode;
    //Corresponds to the LDG's first created node. This could be an if-node 
    //(in case psource is a decision point) or a transition-node
    private Node firstNode = null;
    //List of transition nodes
    private List<Node> transitionNodes = new ArrayList<>();
    //List of places
    private List<Place> places = new ArrayList<>();
    //List that keeps track of processed places
    private List<Place> processedPlaces = new ArrayList<>();
    //List that keeps track of places that are decision points
    private List<Place> ifPlaces = new ArrayList<>();
    //Map that keeps track of transition nodes with the same control dependencies
    private Map<Node, List<Node>> sameCtrlDeps = new HashMap<>();
    //Map that keeps track of nodes control dependent on predicateNodes
    private Map<Node, List<Node>> conditions = new HashMap<>();
    //Map that keeps track of the closest place in which all 
    //the outgoing transitions merge again.
    private Map<Place, List<Node>> joins = new HashMap<>();
    //Incidence matrix
    private DoubleMatrix2D incMatrix;
    //Petrinet of mined log
    private PetriNet petriNet = null;
    //CJC nueva version
    private Ldg ldg2;

    /**
     * Constructor. Uses a Log object internally.
     *
     * @param myLog
     */
    public LdgAlgorithmForProM(Log myLog) {
        this.myLog = myLog;
    }

    @Override
    public Ldg generateLdg(Petrinet petrinet) {
        ldg2 = new AuLdg();
        generateControlDependenceLdg(petrinet);
        addDataDependencies(myLog);
        return ldg2;
    }

    /**
     * Generates the control dependencies edges of the LDG. It uses the Petrinet
     * and gets the incidence matrix which contains the relationships between
     * places and transitions.
     *
     * @param petrinet
     */
    private Ldg generateControlDependenceLdg(Petrinet petrinet) {
        Object obj = petrinet.getInnerPetriNet();

        if (obj instanceof PetriNet) {
            petriNet = (PetriNet) obj;
        }

        places = petriNet.getPlaces();
        incMatrix = petriNet.getIncidenceMatrix();

        List<Transition> transitions = petriNet.getTransitions();

        //TODO
        // Security checks: is psource unique, is psink unique... psource has at least one outgoing transition other checks 

        // One Node for each transition is created nodes are created
        for (int i = 0; i < transitions.size(); i++) {
            //transitionNodes.add(new AuTransitionNode(transitions.get(i).getIdentifier(), 1)); //quitar
            String nodeLabel = transitions.get(i).getIdentifier();
            nodeLabel = nodeLabel.substring(0, nodeLabel.indexOf("\\n"));
            AuTransitionNode tNode = new AuTransitionNode(nodeLabel);
            transitionNodes.add(tNode);
            ldg2.addNode(tNode);
        }

        // Adds places with more than one outgoing transitions to structure IFPLACES
        for (int i = 0; i < places.size(); i++) {
            Place place = places.get(i);
            List<Node> oNodes = getOutgoingTNodes(place);
            if (oNodes.size() > 1) {
                ifPlaces.add(place);

            }
        }

        // Adds all the nodes created from transitions
        //ldg.setTransitionNodes(transitionNodes);

        // Adds an entry node
        entryNode = new AuEntryNode();
        entryNode.setProcessed(true);  //quitar
        //ldg.setEntryNode(entryNode); //quitar
        ldg2.addNode(entryNode); //nueva version
        scanControlDependencies(incMatrix);
        return ldg2;
    }

    /**
     * Generates the data dependencies edges of the LDG. It uses the already
     * created log object.
     *
     * @param myLog
     */
    private Ldg addDataDependencies(Log myLog) {

        LogReader logreader = null;
        if (myLog instanceof ProMLog) {
            logreader = (LogReader) myLog.getInnerLog();
        }

        //LogReader logreader = LogReaderCreator.openLog(dataBean.getPathToFile());
        List<ProcessInstance> instances;
        ProcessInstance instance;
        AuditTrailEntryList events;
        AuditTrailEntry event;
        Map<String, String> attributes;
        Set<String> keySet;
        String[] attrArray;
        String attrKey;
        List<Node> nodes;
        Node nodeSource = null;
        Node nodeDest = null;
        String lastModifier = "";
        DataObjects dataObjects = new DataObjects();

        instances = logreader.getInstances();
        for (int i = 0; i < instances.size(); i++) {

            instance = instances.get(i);
            instance.getAttributes(); //Atributos generales de la instancia(case)
            events = instance.getAuditTrailEntryList();

            for (int j = 0; j < events.size(); j++) {
                try {
                    event = events.get(j);
                    //attributes = event.getAttributes();
                    attributes = event.getAttributes();
                    keySet = attributes.keySet();
                    attrArray = (String[]) keySet.toArray(new String[keySet.size()]);

                    for (int k = 0; k < attrArray.length; k++) {
                        attrKey = attrArray[k];

                        if (attrKey.length() > 6 && attrKey.substring(0, 6).equals("Input_")) {
                            if (dataObjects.existInDataObjects(String.valueOf(attrKey), "I")) {
                                //If data object already exists, and lastModifier is not empty
                                //then add data dependence lastModifiedEvent -> currentEvent
                                //and update lastModifier for this dataObject
                                lastModifier = dataObjects.getLastModifier(String.valueOf(attrKey), "I");
                                if (!lastModifier.isEmpty()) {


                                    //TODO cambiar esta busqueda por un metodo en AuLDG para buscar nodo por label
                                    //pero primero cambiar el label de node!!! para que este solo el nombre, sin el complete!
                                    nodes = ldg2.getTransitionNodes();
                                    if (event.getName().equals(lastModifier)) {
                                        lastModifier = dataObjects.getSecondToLastModifier(String.valueOf(attrKey), "I");
                                    }
                                    for (int p = 0; p < nodes.size(); p++) {
                                        if (nodes.get(p).getLabel().startsWith(event.getName())) {
                                            nodeDest = nodes.get(p);
                                        } else if (nodes.get(p).getLabel().startsWith(lastModifier)) {
                                            nodeSource = nodes.get(p);
                                        }

                                    }
                                    if (nodeSource == null || nodeDest == null) {
                                    } else {
                                        //AuDataDependenceEdge dataDepEdge = new AuDataDependenceEdge(nodeSource, nodeDest);
                                        //dataDepEdge.addDataObject(String.valueOf(attrKey).substring(6), instance.getName());
                                        //dataDepEdge.setLabel(String.valueOf(attrKey));
                                        //ldg.addDataDepEdge(dataDepEdge);
                                        if (existEdgeInLdg(nodeSource, nodeDest) != null) {
                                            AuDataDependenceEdge dataDepEdge = (AuDataDependenceEdge) existEdgeInLdg(nodeSource, nodeDest);
                                            dataDepEdge.addDataObject(new AuLdgData(String.valueOf(attrKey).substring(6)), instance.getName());
                                        } else {
                                            AuDataDependenceEdge dataDepEdge = new AuDataDependenceEdge(nodeSource, nodeDest, String.valueOf(attrKey));
                                            dataDepEdge.addDataObject(new AuLdgData(String.valueOf(attrKey).substring(6)), instance.getName());
                                            ldg2.addEdge(nodeSource.getId(), dataDepEdge);
                                        }
                                    }



                                }
                                //dataObjects.updateLastModifier(String.valueOf(attrKey), event.getName(), "I");
                            } else {
                                //If dataObject doesn't exist, add to dataObject and lastModifier=""
                                //because is Input.
                                dataObjects.addObject(String.valueOf(attrKey), "", "", "I");

                            }
                        } else if (attrKey.length() > 7 && attrKey.substring(0, 7).equals("Output_")) {
                            if (dataObjects.existInDataObjects(String.valueOf(attrKey), "O")) {
                                //If data object already exists, because is Output 
                                //just update lastModifier
                                dataObjects.updateLastModifier(String.valueOf(attrKey), event.getName(), "O");
                            } else {
                                //If dataObject doesn't exist, add to dataObject and lastModified=currentEvent
                                dataObjects.addObject(String.valueOf(attrKey), event.getName(), "", "O");
                            }

                        }
                    }

                } catch (IndexOutOfBoundsException ex) {
                    Logger.getLogger(LdgAlgorithmForProM.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(LdgAlgorithmForProM.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return ldg2;
    }

    /**
     * Method that checks if a given edge is already part of the LDG.
     *
     * @param sourceNode
     * @param targetNode
     * @return
     */
    public Edge existEdgeInLdg(Node sourceNode, Node targetNode) {
        List<Edge> dataDep = ldg2.getDataDepEdges();
        Edge e;
        for (int i = 0; i < dataDep.size(); i++) {
            e = dataDep.get(i);
            Node source = e.getSource();
            Node target = e.getTarget();
            if (source == sourceNode && target == targetNode) {
                return e;
            }
        }
        return null;
    }

    /**
     * Method that builds the control dependencies from the incidence matrix.
     *
     * @param incMatrix
     */
    private void scanControlDependencies(DoubleMatrix2D incMatrix) {
        Place firstPlace = places.get(0);

        //We start with PSOURCE
        if (firstPlace.getIdentifier().equals("psource")) {
            addEntryInProcessedPlaces(firstPlace);
            List<Node> outgoingTNodes = getOutgoingTNodes(firstPlace);

            if (outgoingTNodes.size() == 1) {
                Node node = outgoingTNodes.get(0);
                node.setProcessed(true);
                //we save the node in the hash table
                sameCtrlDeps.put(node, new ArrayList<Node>());
                firstNode = node;
            } else if (outgoingTNodes.size() > 1) {
                //Node ifNode = new AuIfNode();
                AuIfNode ifNode = new AuIfNode();
                firstNode = ifNode;
                connectIfNode(ifNode, places.get(0));

                //we save the node in the hash table
                sameCtrlDeps.put(ifNode, new ArrayList<Node>());
            }

            boolean allProcessed = false;

            while (allProcessed == false) {
                allProcessed = true;
                for (int i = 0; i < transitionNodes.size(); i++) {
                    processTransitionNode(transitionNodes.get(i));
                    if (transitionNodes.get(i).isProcessed() == false) {
                        allProcessed = false;
                    }
                }
            }
        }
        //final step!
        connectEntryNode();
    }

    /**
     * Method that analyzes the current processed transition and distinguish
     * different cases based on the number of incoming places, and then based on
     * the number of incoming and outgoing transitions the incoming place has.
     *
     * @param node
     */
    private void processTransitionNode(Node node) {
        //This condition is applied to the tNodes which follow PSOURCE
        if (node.isProcessed() == true) {
        } else {
            List<Place> incomingPlaces = getIncomingPlaces(node);
            for (int i = 0; i < incomingPlaces.size(); i++) {
                Place incomingPlace = incomingPlaces.get(i);
                List<Node> incomingTNodes = getIncomingTNodes(incomingPlace);
                for (int k = 0; k < incomingTNodes.size(); k++) {
                    Node iNode = incomingTNodes.get(k);
                    if (iNode.isProcessed() == false) {
                        processTransitionNode(iNode);
                    }
                }

            }

            //HAS ONLY ONE INCOMING PLACE
            //CASE A (4 cases)
            if (incomingPlaces.size() == 1) {
                Place place = incomingPlaces.get(0);
                List<Node> incomingTNodes = getIncomingTNodes(place);
                List<Node> outgoingTNodes = getOutgoingTNodes(place);

                //CASE A.1-1
                // [1]------>()------->[]node
                if (incomingTNodes.size() == 1 && outgoingTNodes.size() == 1) {
                    Node incomingNode = incomingTNodes.get(0);
                    processCaseOneToOne(node, incomingNode);
                    node.setProcessed(true);
                }
                //CASE A.1-N
                // [1]------>()------->[ ]node
                //            \------->[ ]
                //             \------>[ ]				
                if (incomingTNodes.size() == 1 && outgoingTNodes.size() > 1) {
                    Node incomingNode = incomingTNodes.get(0);
                    processCaseOneToN(node, place, incomingNode);
                    node.setProcessed(true);
                }
                //CASE A.N-1
                // [ ]------>()------->[1]node
                // [ ]-------/
                // [ ]------/
                if (incomingTNodes.size() > 1 && outgoingTNodes.size() == 1) {
                    if (joins.containsKey(place)) {
                        processCaseNToOne(node, place);
                        node.setProcessed(true);
                    } else {
                        if (!ifPlaces.isEmpty()) {
                        } else {
                            addEntryInCDepMap(firstNode, node);
                        }
                    }
                }
                //CASE A.N-N
                // [ ]------>()------->[1]node
                // [ ]-------/\------->[ ]
                // [ ]------/  \------>[ ]
                if (incomingTNodes.size() > 1 && outgoingTNodes.size() > 1) {
                    if (joins.containsKey(place)) {
                        processCaseNToN(node, place);
                        node.setProcessed(true);
                    } else {
                        if (!ifPlaces.isEmpty()) {
                        } else {
                            addEntryInCDepMap(firstNode, node);
                        }
                    }
                }
                addEntryInProcessedPlaces(place);
            } //incoming places > 1
            //CASE B (4 cases)
            else {
                boolean canBeProcessed = true;
                for (int i = 0; i < incomingPlaces.size(); i++) {

                    Place place = incomingPlaces.get(i);
                    if (!processedPlaces.contains(place)) {
                        List<Node> incomingTNodes = getIncomingTNodes(place);
                        List<Node> outgoingTNodes = getOutgoingTNodes(place);

                        //CASE A.1-1
                        // [1]------>()------->[]node
                        if (outgoingTNodes.size() == 1 && incomingTNodes.size() == 1) {
                            Node incomingNode = incomingTNodes.get(0);
                            processCaseOneToOne(node, incomingNode);
                            addEntryInProcessedPlaces(place);
                        }
                        //CASE A.1-N
                        // [1]------>()------->[ ]node
                        //            \------->[ ]
                        //             \------>[ ]				
                        if (outgoingTNodes.size() > 1 && incomingTNodes.size() == 1) {
                            Node incomingNode = incomingTNodes.get(0);
                            processCaseOneToN(node, place, incomingNode);
                            addEntryInProcessedPlaces(place);
                        }
                        //CASE A.N-1
                        // [ ]------>()------->[1]node
                        // [ ]-------/
                        // [ ]------/
                        if (outgoingTNodes.size() == 1 && incomingTNodes.size() > 1) {
                            if (joins.containsKey(place)) {
                                processCaseNToOne(node, place);
                                addEntryInProcessedPlaces(place);
                            } else {
                                if (!ifPlaces.isEmpty()) {
                                    canBeProcessed = false;
                                } else {
                                    addEntryInCDepMap(firstNode, node);
                                    addEntryInProcessedPlaces(place);
                                }
                            }
                        }
                        //CASE A.N-N
                        // [ ]------>()------->[1]node
                        // [ ]-------/\------->[ ]
                        // [ ]------/  \------>[ ]
                        if (outgoingTNodes.size() > 1 && incomingTNodes.size() > 1) {
                            if (joins.containsKey(place)) {
                                processCaseNToN(node, place);
                                addEntryInProcessedPlaces(place);
                            } else {
                                if (!ifPlaces.isEmpty()) {
                                    canBeProcessed = false;
                                } else {
                                    addEntryInCDepMap(firstNode, node);
                                    addEntryInProcessedPlaces(place);
                                }
                            }
                        }

                    }
                }
                if (canBeProcessed == true) {
                    node.setProcessed(true);
                    for (int i = 0; i < incomingPlaces.size(); i++) {
                        Place place = incomingPlaces.get(i);
//						List<Node> outgoingTNodes = getOutgoingTNodes(place);
//						if(outgoingTNodes.size()>1){
//							for (int k=0; k <outgoingTNodes.size(); k++){
//								outgoingTNodes.get(k).setProcessed(true);
//							}
//						}							
                    }
                }
//				else{
//				}
            }

        }
    }

    /**
     * Solves case 1.1 (for both A and B).
     *
     * @param transitionNode
     * @param incomingNode
     */
    private void processCaseOneToOne(Node transitionNode, Node incomingNode) {

        if (conditions.containsKey(incomingNode)) {
            List<Node> conditionalNodes = conditions.get(incomingNode);
            for (int i = 0; i < conditionalNodes.size(); i++) {
                Node predicateNode = conditionalNodes.get(i);
                //Edge predTNodeEdge = new AuControlDependenceEdge(predicateNode, transitionNode);
                AuControlDependenceEdge predTNodeEdge = new AuControlDependenceEdge(predicateNode, transitionNode, "PR");
                //ldg.addCDepEdge(predTNodeEdge);
                ldg2.addEdge(predicateNode.getId(), predTNodeEdge);
                //create new entry for conditions
                addEntryInConditionsMap(transitionNode, predicateNode);
            }
        } else {
            addEntryInCDepMap(incomingNode, transitionNode);
        }
    }

    /**
     * Solves case 1.N (for both A and B).
     *
     * @param transitionNode
     * @param place
     * @param incomingTNode
     */
    private void processCaseOneToN(Node transitionNode, Place place, Node incomingTNode) {

        //Node ifNode = new AuIfNode();
        AuIfNode ifNode = new AuIfNode();

        connectIfNode(ifNode, place);

        if (conditions.containsKey(incomingTNode)) {
            List<Node> conditionalNodes = conditions.get(incomingTNode);
            for (int i = 0; i < conditionalNodes.size(); i++) {
                Node predicateNode = conditionalNodes.get(i);
                //Edge predIfNodeEdge = new AuControlDependenceEdge(predicateNode, ifNode);
                AuControlDependenceEdge predIfNodeEdge = new AuControlDependenceEdge(predicateNode, ifNode, "PCOTN");
                //ldg.addCDepEdge(predIfNodeEdge);
                ldg2.addEdge(predicateNode.getId(), predIfNodeEdge);
                //create new entry for conditions
                addEntryInConditionsMap(ifNode, predicateNode);
            }
        } else {
            addEntryInCDepMap(incomingTNode, ifNode);
        }
    }

    /**
     * Solves case N.1 (for both A and B).
     *
     * @param transitionNode
     * @param place
     */
    private void processCaseNToOne(Node transitionNode, Place place) {
        List<Node> ifNodes = joins.get(place);
        addEntryInCDepMap(ifNodes.get(0), transitionNode);
    }

    /**
     * Solves case N.N (for both A and B).
     *
     * @param transitionNode
     * @param place
     */
    private void processCaseNToN(Node transitionNode, Place place) {
        //Node ifNode = new AuIfNode();
        AuIfNode ifNode = new AuIfNode();
        connectIfNode(ifNode, place);
        List<Node> ifNodes = joins.get(place);
        addEntryInCDepMap(ifNodes.get(0), ifNode);
    }

    /**
     * Connects the entry node with all the first node and then with all the
     * nodes that inherited the same control dependencies.
     *
     */
    private void connectEntryNode() {
        List<Node> nodesToConnect = new ArrayList<>();
        //Edge entryToTNode = new AuControlDependenceEdge(entryNode, firstNode);
        AuControlDependenceEdge entryToTNode = new AuControlDependenceEdge(entryNode, firstNode, "EN");
        //ldg.addCDepEdge(entryToTNode);
        ldg2.addEdge(entryNode.getId(), entryToTNode);
        nodesToConnect.addAll(sameCtrlDeps.get(firstNode));
        for (int i = 0; i < nodesToConnect.size(); i++) {
            //entryToTNode = new AuControlDependenceEdge(entryNode, nodesToConnect.get(i));
            entryToTNode = new AuControlDependenceEdge(entryNode, nodesToConnect.get(i), "EN");
            //ldg.addCDepEdge(entryToTNode);
            ldg2.addEdge(entryNode.getId(), entryToTNode);

            if (sameCtrlDeps.containsKey(nodesToConnect.get(i))) {
                nodesToConnect.addAll(sameCtrlDeps.get(nodesToConnect.get(i)));
            }
        }
    }

    /**
     * The place is a decision point and therefore it's necessary to create the
     * corresponding predicate nodes and create the edges between them and all
     * the outgoing transition-nodes.
     *
     * @param ifNode
     * @param place
     */
    private void connectIfNode(Node ifNode, Place place) {
        if (ifPlaces.contains(place)) {
            ifPlaces.remove(place);
        }
        //TODO
        //It is implicit that this cannot be PSINK. Should I check it?
        ifNode.setProcessed(true);
        //Add the new just created node:
        //ldg.addIfNode(ifNode);
        ldg2.addNode(ifNode);
        List<Node> outgoingTNodes = getOutgoingTNodes(place);

        for (int i = 0; i < outgoingTNodes.size(); i++) {
            Node oNode = outgoingTNodes.get(i);
            //Node predicateNode = new AuPredicateNode();
            AuPredicateNode predicateNode = new AuPredicateNode("C");
            predicateNode.setProcessed(true);
            predicateNode.setInnerPlace(place.getIdentifier());
            //ldg2.addNode(ifNode);
            ldg2.addNode(predicateNode);
            //Add the new just created node:
            //Edge ifPredEdge = new AuControlDependenceEdge(ifNode, predicateNode);
            AuControlDependenceEdge ifPredEdge = new AuControlDependenceEdge(ifNode, predicateNode, "PR");
            ldg2.addEdge(ifNode.getId(), ifPredEdge);
            //Add the new just created edge:
            //Edge predTNodeEdge = new AuControlDependenceEdge(predicateNode, outgoingTNodes.get(i));
            AuControlDependenceEdge predTNodeEdge = new AuControlDependenceEdge(predicateNode, outgoingTNodes.get(i), "PR");
            ldg2.addEdge(predicateNode.getId(), predTNodeEdge);
            //Add the new just created edge:
            //we add the relation between a node and his predicate in the Map conditions.
            addEntryInConditionsMap(outgoingTNodes.get(i), predicateNode);
            if (getIncomingPlaces(oNode).size() == 1) {
                oNode.setProcessed(true);
            }
            //ldg.addPredicateNode(predicateNode);					
            //ldg2.addNode(predicateNode);
            //ldg.addCDepEdge(ifPredEdge);
            //ldg2.addEdge(ifNode.getId(), ifPredEdge);
            //ldg.addCDepEdge(predTNodeEdge);
            //ldg2.addEdge(predicateNode.getId(), predTNodeEdge);


        }
        Place endingPlace = getCommonMergingPlace(place);
        addEntryInJoins(endingPlace, ifNode);
    }

    /**
     * Adds a places to the list of processed places.
     *
     * @param place
     */
    private void addEntryInProcessedPlaces(Place place) {
        if (!processedPlaces.contains(place)) {
            processedPlaces.add(place);
        }
    }

    /**
     * Adds to the map a new pair where node has a control dependency on
     * newConditionalNode, which is a predicate-node.
     *
     * @param node
     * @param newConditionalNode
     */
    private void addEntryInConditionsMap(Node node, Node newConditionalNode) {

        List<Node> conditionalNodes = null;
        if (conditions.containsKey(node)) {
            conditionalNodes = conditions.get(node);
            if (!conditionalNodes.contains(newConditionalNode)) {
                conditionalNodes.add(newConditionalNode);
            }
        } else {
            conditionalNodes = new ArrayList<>();
            conditionalNodes.add(newConditionalNode);
            conditions.put(node, conditionalNodes);
        }
    }

    /**
     * Adds to the map a new pair where node inherits the same control
     * dependency of newSameLevelNode.
     *
     * @param node
     * @param newSameLevelNode
     */
    private void addEntryInCDepMap(Node node, Node newSameLevelNode) {
        List<Node> sameLevelNodes = null;
        if (sameCtrlDeps.containsKey(node)) {
            sameLevelNodes = sameCtrlDeps.get(node);
            if (!sameLevelNodes.contains(newSameLevelNode)) {
                sameLevelNodes.add(newSameLevelNode);
            }
        } else {
            sameLevelNodes = new ArrayList<>();
            sameLevelNodes.add(newSameLevelNode);
            sameCtrlDeps.put(node, sameLevelNodes);
        }
    }

    /**
     * Adds to the map a new pair where place is the place where all the
     * outgoing transition-nodes of ifNode converge.
     *
     *
     * @param place
     * @param ifNode
     */
    private void addEntryInJoins(Place place, Node ifNode) {
        List<Node> ifNodes = null;
        if (joins.containsKey(place)) {
            ifNodes = joins.get(place);
            if (!ifNodes.contains(ifNode)) {
                ifNodes.add(ifNode);
            }
        } else {
            ifNodes = new ArrayList<>();
            ifNodes.add(ifNode);
            joins.put(place, ifNodes);
        }
    }

    /**
     * Gets the place's outgoing transitions.
     *
     * @param place
     */
    private List<Node> getOutgoingTNodes(Place p) {
        List<Node> outgoingTNodes = new ArrayList<>();
        int columnNumber = places.lastIndexOf(p);

        DoubleMatrix1D column = incMatrix.viewColumn(columnNumber);
        for (int i = 0; i < column.size(); i++) {
            Double mValue = column.get(i);
            if (mValue < 0) {
                outgoingTNodes.add(transitionNodes.get(i));
            }
        }
        return outgoingTNodes;
    }

    /**
     * Gets the place's incoming transitions.
     *
     * @param place
     */
    private List<Node> getIncomingTNodes(Place p) {
        List<Node> incomingTNodes = new ArrayList<>();
        int columnNumber = places.lastIndexOf(p);

        DoubleMatrix1D column = incMatrix.viewColumn(columnNumber);
        for (int i = 0; i < column.size(); i++) {
            Double mValue = column.get(i);
            if (mValue > 0) {
                incomingTNodes.add(transitionNodes.get(i));
            }
        }
        return incomingTNodes;
    }

    /**
     * Given a transition-node it returns the outgoing places.
     *
     * @param transitionNode
     */
    private List<Place> getOutgoingPlaces(Node transitionNode) {
        List<Place> outgoingPlaces = new ArrayList<>();
        int rowNumber = transitionNodes.lastIndexOf(transitionNode);

        DoubleMatrix1D row = incMatrix.viewRow(rowNumber);
        for (int i = 0; i < row.size(); i++) {
            Double mValue = row.get(i);
            if (mValue > 0) {
                outgoingPlaces.add(places.get(i));
            }
        }
        return outgoingPlaces;
    }

    /**
     * Given a transition-node it returns the incoming places.
     *
     * @param transitionNode
     */
    private List<Place> getIncomingPlaces(Node transitionNode) {
        List<Place> incomingPlaces = new ArrayList<>();
        int rowNumber = transitionNodes.lastIndexOf(transitionNode);

        DoubleMatrix1D row = incMatrix.viewRow(rowNumber);
        for (int i = 0; i < row.size(); i++) {
            Double mValue = row.get(i);
            if (mValue < 0) {
                incomingPlaces.add(places.get(i));
            }
        }
        return incomingPlaces;
    }

    /**
     * Given a place it finds the place where all its outgoing transitions
     * converge.
     *
     * @param place
     */
    private Place getCommonMergingPlace(Place place) {
        //TODO
        //Here I'm expecting the place to have more than one outgoing edge
        Place merge = null;
        int maxSizeList = 0;
        int minSizeList = 1000000;
        List<Place> candidatePlaces = null;
        Map<Node, List<Place>> placesMap = new HashMap<>();
        List<Node> outgoingTNodes = getOutgoingTNodes(place);
        for (int i = 0; i < outgoingTNodes.size(); i++) {
            List<Place> placesToSink = getAllPlacesToSink(outgoingTNodes.get(i));
            if (placesToSink.size() > maxSizeList) {
                maxSizeList = placesToSink.size();
            }
            if (placesToSink.size() < minSizeList) {
                minSizeList = placesToSink.size();
                candidatePlaces = placesToSink;
            }
            placesMap.put(outgoingTNodes.get(i), placesToSink);
        }
        //Now is time to compare the resulting places:
        Iterator it = placesMap.keySet().iterator();

        while (it.hasNext()) {
            Node key = (Node) it.next();
            List<Place> mapPlaces = placesMap.get(key);
            List<Place> placesToBeRemoved = new ArrayList<Place>();
            for (int i = 0; i < candidatePlaces.size(); i++) {
                if (!mapPlaces.contains(candidatePlaces.get(i))) {
                    placesToBeRemoved.add(candidatePlaces.get(i));
                }
            }
            for (int i = 0; i < placesToBeRemoved.size(); i++) {
                candidatePlaces.remove(placesToBeRemoved.get(i));
            }
        }

        return candidatePlaces.get(0);
    }

    private List<Place> getAllPlacesToSink(Node transitionNode) {
        Place sink = places.get(1);
        List<Place> allPlaces = new ArrayList<>();
        List<Place> outgoingPlaces = getOutgoingPlaces(transitionNode);

        for (int i = 0; i < outgoingPlaces.size(); i++) {
            Place place = outgoingPlaces.get(i);
            if (place != sink) {
                if (outgoingPlaces.size() == 1) {
                    allPlaces.add(place);
                }
                List<Node> outgoingTNodes = getOutgoingTNodes(place);
                for (int j = 0; j < outgoingTNodes.size(); j++) {
                    List<Place> resultingPlaces = getAllPlacesToSink(outgoingTNodes.get(j));
                    for (int k = 0; k < resultingPlaces.size(); k++) {
                        Place resultingPlace = resultingPlaces.get(k);
                        if (!allPlaces.contains(resultingPlace)) {
                            allPlaces.add(resultingPlace);
                        }
                    }
                }
            } else if (!allPlaces.contains(sink)) {
                allPlaces.add(sink);
            }
        }
        return allPlaces;
    }
}
